#!/usr/bin/env python2
#-*- coding: utf-8 -*-

import json
import os
import re
import datetime
import time
import random

from Ump import utils
from Ump.common import config
from Ump.common import exception
from Ump.common import log
from Ump.objs.db import models
from Ump.objs.db.models import Cluster, Host

from Ump.objs.manager_base import Manager

from Ump.objs.session_wrapper import enable_log_and_session, _sw

from Ump.lich.cluster import LichClusterParam, LichCluster
from Ump.lich.syncump import LichSyncumpParam, LichSyncump
from Ump.lich.shell import LichShell, LichShellParam

from agentapi import cluster as cluster_api


LOG = log.get_log('Ump.objs.cluster.manager')


@models.add_model(models.Cluster)
class ClusterManager(Manager):

    def __init__(self):
        super(ClusterManager, self).__init__()
        self.type = 'cluster'
        self.lich_cluster = LichCluster()
        self.lich_syncump = LichSyncump()
        self.lichShell = LichShell()

    def init_lichconfig(self, kwargs):
        cmd = cluster_api.ConfigInitCmd()
        cmd.networks = kwargs.get('networks')
        cmd.iscsi_vip = kwargs.get('iscsi_vip')
        cmd.iqn = kwargs.get('iqn') or self._create_iqn()
        cmd.nohosts = kwargs.get('nohosts')
        cmd.host = kwargs.get('host')
        self.taskm.sync_post(cmd)

    def _create_iqn(self):
        today = datetime.datetime.now()
        iqn = 'iqn.%s-%s.com.fusionstack' % (today.isoformat()[:7], random.randint(1000, 9999))
        return iqn

    def health(self, *args, **kw):
        host_ip = self.lich_cluster._select_host()
        kwargs = LichClusterParam(host_ip=host_ip)
        return self.lich_cluster.health(kwargs)

# /* The following commented out blocks are not used temporary */

    @enable_log_and_session(resource='sync', event='sync', disable_oplog=True)
    def _create(self, _logger, kwargs):
        LOG.info('------------------------')
        LOG.info('create function',kwargs)
        LOG.info('------------------------')
        cluster = self.create(**kwargs)

    def create(self, cluster_type, node_ip, passwd, option, isforce=False, repnum=2, parent_id=None,
                cluster_id=None, chapname = None, password = None, cluster_name=None, iqn=None, name=None, **kwargs):

        lich_nodes_ip = []
        values = {}

        port = ''
        hostnames = []
        if option  == 'import':
            self._check_authentication(node_ip, password=passwd)
            #self._deploy_agent(node_ip, passwd)

            #res = self._exec_remote(node_ip,'ls',iskey=True,passwd=passwd)

            hostname = res = self._exec_remote(node_ip, 'hostname', password=passwd)
            param =  LichShellParam(node_ip, password=passwd)

            filename = "%s/lich/admin/cluster.py" % self.lich_home
            if not self.lichShell.file_exists(param, filename):
                msg_err = '输入主机未安装存储，无法导入'
                raise Exception(msg_err)

            config_dict = self.lich_cluster.dump_config(param)
            networks = self.lich_syncump.get_network(param)
            self.lich_home = config_dict['globals.home']
            link_hostname = config_dict['globals.hostname']
            cluster_name = config_dict['globals.clustername']
            values.update({'cluster_name':cluster_name, 'home':self.lich_home})

            listnode_dict = self.lich_cluster.listnode(param)
            hostnames = listnode_dict.keys()
            if link_hostname.strip() not in hostnames:
                raise Exception('主机（%s）不在集群内，请检查集群主机配置' % node_ip)

            res = self.lich_syncump.eth_hosts(param)
            hosts_conf = res.split('\n')
            is_nohosts = config_dict['globals.nohosts'] == 'on'
            lich_nodes_ip = self.check_valid_listnode(listnode_dict, hosts_conf, networks, is_nohosts)
            repnum = 2

            if lich_nodes_ip  == []:
                msg_err = '集群内没有发现可用主机'
                LOG.error(msg_err, extra={'cluster_id': parent_id})
                raise Exception(msg_err)

        values.update({
                'parent_id': None,
                'iqn': iqn,
                'name': name,
                'type': cluster_type,
                'repnum': repnum,
                'port': port.strip(),
                'cluster_name': cluster_name,
                })

        from Ump.objs.host.manager import HostManager
        from Ump.objs.iops.manager import IopsManager
        hmanager, iom = HostManager(), IopsManager()
        if cluster_id is not None:
            cluster = self._get_one(cluster_id)
            cluster.update(values)
        else:
            cluster = models.Cluster(values).save()
        self.isexsit_hostname(hostnames, cluster)

        for host_ in lich_nodes_ip:
            res = self._ssh_key(host_, passwd)
#            if not host_ == node_ip:
#                self._deploy_agent(host_, passwd)

            namecmd = 'hostname'
            cmd = '%s/lich/admin/syncump.py _exec_remote --hostname %s --cmd %s'%(self.lich_home,host_,namecmd)
            try:
                hostname = self.api_sync_call(node_ip, cmd)
            except Exception,e:
                hostname = ''
                pass

            host_values = {
                'hostname': hostname,
                'ip': host_,
                'name': hostname,
                'cluster_id': cluster.id,
                'is_join': True,
                'status': 'running'
            }
            if hostname == link_hostname:
                host_values.update({'link_ips':json.dumps([node_ip])})
            host = models.Host(host_values).save()
            cluster.hosts.append(host)

        options = '创建'
        args = {'cluster_id':cluster.id, 'ssh_ip':node_ip, 'hostips':lich_nodes_ip}
        if option  == 'import':
            options = '导入'
        iom.default_io(cluster.id)
#        self.ump_sync(cluster.id)
        return cluster

    def isexsit_hostname(self, hostnames, cluster):
        cluster_hostnames = [host.name for host in cluster.hosts if host.name]
        dobule_names = list(set(hostnames).intersection(set(cluster_hostnames)))
        if dobule_names:
            err = 'Hostname需唯一，已有集群已经存在%s' % (",".join(dobule_names))
            raise exception.HostnameDuplica(err)

    def cluster_host_sync(self, cluster_id) :
        """添加缺失主机，删除不存在主机
        """
        cluster = Cluster.query.filter_by(id=cluster_id).first()

        cmd = cluster_api.ClusterSyncCmd()
        cmd.target_id = cluster_id
        cmd.is_show = False

        self.taskm.async_post(cmd, callback=self.cluster_host_sync_callback)

    def cluster_host_sync_callback(self, rsp):
        cluster_id = rsp.target_id

        cluster = Cluster.query.first()
        cluster = self._sync_config(cluster, rsp.configdump)

        remote_host_ips = []
        if rsp.hosts:
            remote_host_ips = json.loads(rsp.hosts)

        local_hosts = [host.ip for host in cluster.hosts if host.ip]
        remote_host_ips = []

        clusterhosts = cluster.hosts
        for index, host in enumerate(clusterhosts):
            if host.lichd_status == 'joining':
                return 

            last_element_flag = index == len(clusterhosts) - 1

            if remote_host_ips and host.ip not in remote_host_ips:
                if not last_element_flag:
                    LOG.warn("node %s deleted by sync :remote_ips-%s" % (host.ip, str(remote_host_ips)))
                    host.delete()

        for host_ip in remote_host_ips:
            host = Host.query.filter_by(ip=host_ip).first()
            if not host and host_ip not in local_hosts:
                values = {
                    'ip': host_ip,
                    'cluster_id': 1,
                }
                new_host = Host(values).save()


    def _sync_health(self, cluster, param):
        health_values = self.lich_cluster.health(param)
        health_values['cluster_id'] = cluster.id
        if cluster.health:
            cluster.health[0].update(health_values)
        else:
            models.Health(health_values).save()
        return  cluster

    def _sync_config(self, cluster, configdict):
        if not configdict:
            return cluster

        self.create_synconfig(configdict)
        cluster_values = {
            'home': configdict.get('globals.home'),
            'nbd_root': configdict.get('globals.nbd_root', 'default'),
            'lichbd_root': configdict.get('globals.lichbd_root', 'default'),
            'cluster_name': configdict.get('globals.clustername'),
            'iqn ': configdict.get('iscsi.iqn'),
            'config_str': json.dumps(configdict)
        }

        cluster.update(cluster_values) 
        return cluster

    def create_synconfig(self, configdict):
        for k,v in configdict.iteritems():
            module = k.split('.')[0]
            key = k.split('.')[1]
            values = {
                'module': module,
                'key': key,
                'value':v,
            }
            sysconfig = _sw.get_one(models.Sysconfig, id_or_spec={'key':key, 'module':module})
            if sysconfig:
                sysconfig.update(values)
            else:
                models.Sysconfig(values).save()
        
    def _sync_networks(self, cluster, param):
        network = self.lich_syncump.get_network(param)
        values = {'network':network.strip()} 
        cluster.update(values) 
        return cluster
        
    def _sync_iscsi_port(self, cluster, param):
        port = self.lich_syncump.iscsi_port(param, 'check')
        cluster.update({'iscsi_port':port.strip()})
        return cluster
    
    def _get_param(self, cluster_id):
        target_ip = self.lich_cluster._select_host(cluster_id)
        param = LichClusterParam(host_ip=target_ip)
        return param
        
    def _get_one(self, cluster_id):
        cluster = _sw.get_one(models.Cluster, id_or_spec=cluster_id)
        if not cluster:
            raise exception.NotFound(_id=cluster_id)
        return cluster

    def _get_lock_detail(self, host, cmd):
        detail = result = ''
        try:
            result = self._exec_remote(host.ip,cmd)
        except Exception,e:
            pass
        if result.find("fusionstack_recover.lock") != -1 or \
                result.find('/var/run/fusionstack_raid_hpacucli.lock') != -1 or result.find('/var/run/deleting.lock') != -1:
            detail = '操作失败，进程处于锁定中，请稍后重试'
        if result.lower().find("the cluster was just beginning, please try again after ten minutes") != -1:
            detail = '集群正在重构，请十分钟后再进行该操作'
        if result.find("no available lichd") != -1:
            detail = '主机服务已全部停止，无法移除磁盘'
        if result.find("need reboot") != -1:
            detail = u'服务进程处于不可中断状态，需重启主机解决'
    
        if result.find("Machine is not on the network") != -1:
            clusterhosts = host.cluster.hosts
            stoped_node = []
            for node in clusterhosts :
                if node.is_host_stopped:
                    stoped_node.append(node.ip)
            if stoped_node:
                detail = u'主机%s服务全部处于停止状态,数据恢复失败'%(','.join(stoped_node))
            else:
                detail = u'存储服务异常,数据恢复失败'
        return detail

    def check_is_deleting(self, host, diskdeleting=True):
        cmd = "ps aux|grep 'lich.node --drop\|admin\/dropnode.sh \|node.py --drop' |grep -v grep"
        drop_process =  utils.exception_pass(self._exec_remote, host.ip, cmd)

        drop_processes = [x for x in drop_process.split("\n") if x ]
        is_deleting = False
        if drop_process and drop_processes :
            is_deleting = True
                
        if diskdeleting and is_deleting:
            host_values = {'lichd_status':'deleting', 'is_deleting':True}
            host.update(host_values)
        return is_deleting


    def check_valid_listnode(self, listnode_dict, hosts_conf, network, is_nohosts):
        lich_nodes_ip = []
        hostnames = listnode_dict.keys()
        for hostname in set(hostnames):
            for host_ in hosts_conf:
                sep = '\t' if '\t' in host_ else  ' '
                hosts_ = host_.split(sep)
                hosts_ = [x for x in hosts_ if x]
                if len(hosts_) < 2:
                    continue
                ip = hosts_[0]
                if not utils.check_network(network, ip):
                    continue

                hostname_ = hosts_[1]
                node = ip if is_nohosts else hostname_
                node = node.strip()
                if node == hostname.strip():
                    lich_nodes_ip.append(ip.strip())
        return lich_nodes_ip


    def get_repnum(self, cluster):
        for host in cluster.hosts :
            repnum_cmd = 'lichfs --attrget / repnum'
            try:
                repnum = self._exec_remote(host.ip, repnum_cmd)
            except Exception,e:
                continue
            if len(repnum) > 2:
                repnum = 2
            if len(cluster.hosts) < 3:
                repnum = 2
            cluster.update({'repnum': repnum})
            break
        return  
        
    def _get_cluster_createtime(self, cluster):
        storage_create_date = ''
        for host in cluster.hosts:
            try:
                cmd = '%s/lich/libexec/lichfs --attrget /system lich_system_createtime' % (self.lich_home)
                res = self._exec_remote(host.ip, cmd)
                if res:
                    storage_create_date = datetime.datetime.fromtimestamp(float(res))
                    cluster.update({'system_createtime':res})
                break
            except Exception, e:
                LOG.info(e)
                LOG.error("get_cluster_createtime error %s,%s" % (e, host.ip))
                pass
        return storage_create_date 

    def license_sync(self, cluster_id=1, ssh_ip=None, hostips=None):
        return self._license_sync(cluster_id=cluster_id, ssh_ip=ssh_ip, hostips=hostips)

    def _license_sync(self, _ss=None, cluster_id=1, ssh_ip=None, hostips=None):
        home = os.path.split(self.defs.UMP_HOME)[0]
        cluster = self._get_one(cluster_id)

        if ssh_ip is None:
            ssh_ip = self._select_host(cluster_id)

        if hostips is not  None:
            self.license_dispatch(cluster_id, hostips)
        else:
            hostips = [host.ip for host in cluster.hosts ]
            self.license_dispatch()

        hostips = sorted(hostips)

        license_node = '/tmp/license_node'
        with open(license_node, 'wr') as out:
            [out.write('%s %s\n'%(va,'')) for va in  hostips]

        is_retry = True
        retry_count = 0
        while is_retry:
            retry_count += 1
            code, res, err = utils._exec_pipe('python %s/manage/manage.py --list %s' % (home, license_node))
            if err and "license_dispatch.lock" in err and retry_count < 16:
                time.sleep(3)
                is_retry = True
            else:
                is_retry = False 
            if err:
                LOG.info(err)

        LOG.info('licese list - %s - %s '%(res,err))
        tail = res.split('\n')
        capacity = '0G'
        isfree = False
        isinvalid = False
        ispermit = 0 
        license_permit = 0 
        license_stat = '未识别'
        
        invalid_date = None
        register_date = None
        returncode, stdout, stderr = utils._exec_pipe('ls --full-time %s/manage/license/*'%home)
        if returncode == 0:
            stds = [x for x in stdout.split('\n') if x]
            rdate = stds[-1]
            rdates = [x for x in rdate.split(' ') if x]
            rdatetime = "%s %s"%(rdates[5],rdates[6].split('.')[0])
            temp_license_create_datetime = datetime.datetime.strptime(rdatetime.strip(), '%Y-%m-%d %H:%M:%S')
            register_date = temp_license_create_datetime

        temp_storage_create_date = ''
        temp_storage_create_date = self._get_cluster_createtime(cluster)

        ispermanent = False
        is_invalid_date = False
        freecount = 0
        for t in tail :
            if not t :
                continue
            expration_index = t.find('license expiration time')
            expired_index = t.find('license expired time')
            if expration_index != -1:
                date = t[expration_index+24:]
            if  expired_index != -1:
                date = t[expired_index+21:]

            if expration_index != -1 or expired_index != -1:
                invalid_datetime = datetime.datetime.strptime(date.strip(),'%Y-%m-%d %H:%M:%S')
                invalid_date = invalid_datetime 
                is_invalid_date = True
            if 'ERROR' in t:
                continue

            if t.startswith('capacity'):
                capacity = t.strip().split(':')[-1] 
            elif t.startswith('Infinite capacity'):
                capacity = 'Infinite capacity'
            elif t.startswith('license expiration time') or t.startswith('license expired time'):
                pass
            elif t.startswith('License expired'):
                pass
            elif t.startswith('Permanent free license'):
                license_stat = 'permanent'  
                ispermanent = True
            else:
                license = t.strip().split(':')[-1] 
                size = license.split(' ')[-1].strip('G')
                host = None
                if not t.startswith('capacity') :
                    host_ip = t.strip().split(':')[0] 
                    try:
                        host = _sw.db_host({'ip':host_ip, 'cluster_id':cluster_id})
                        if 'shutdown' in license or 'offline' in license or 'failed' in license:
                            license_stat = 'permit'
                            offsize = license.split(' ')[-1].strip('G')
                            if not host.license_total:
                                license_permit += int(float(offsize))
                            else:
                                size = int(host.license_total)
                                license_permit += host.license_total
                    except:
                        LOG.error('host_get_with_ip_cluster - %s - %s '%(host_ip, cluster_id))
                        pass
                if host is None:
                    continue

                if 'permit' in license:
                    license_permit += int(float(size))
                    license_stat = 'permit'
                    ispermit = True
                if 'free' in license:
                    license_permit += int(float(size))
                    license_stat = 'free'
                    freecount += 1
                    isfree = True

                delta = datetime.timedelta(days=90)
                if (freecount == len(hostips)) or (temp_storage_create_date != '' and not is_invalid_date):
                    invalid_date = temp_storage_create_date + delta 
                    
                if temp_storage_create_date and register_date is None and not is_invalid_date:
                    register_date = temp_storage_create_date 

                node_stat = license_stat

                cmd = '%s/lich/admin/syncump.py node_date --ip %s'%(self.lich_home, ssh_ip)
                try:
                    res = self._exec_remote(ssh_ip, cmd)
                    today = datetime.datetime.fromtimestamp(float(res))
                except Exception, e:
                    today = ''
                    LOG.info(e)
                    pass

                if 'invalid' in license:
                    isinvalid = True
                    license_stat = 'invalid'
                    if (invalid_date - today).days <= 0 and not ispermanent:
                        node_stat = 'overdate'
                    else:
                        node_stat = 'overquota'

                node_stat_at = host.node_stat_at
                if invalid_date and today:
                    node_stat_at = int((invalid_date - today).days)


                if not isinvalid and isfree:
                    license_stat = 'free'
                    node_stat = 'free'
                if not t.startswith('capacity') :
                    try:
                        host_val = {'node_invalid_at':invalid_date.date(), 'node_register_info':node_stat,'node_stat_at':node_stat_at}
                        host.update(host_val)
                    except Exception,e:
                        LOG.info(e)
                        LOG.error('host_update  - %s - %s '%(host_ip, cluster_id))
                        pass

        if isinvalid:
            license_stat = 'invalid'
        if not isinvalid and isfree:
            license_stat = 'free'

        if not isinvalid and not isfree and ispermit:
            licese_stat = 'permit'
        if ispermanent :
            license_stat = 'permanent'  

        if capacity != 'Infinite capacity':
            if int(capacity.strip('G')) < license_permit:
                licese_stat = 'overquota'

        values = {
            'license_capacity': capacity,
            'license_permit': license_permit,
            'license_stat': license_stat,
            'invalid_date': invalid_date,
            'register_date':register_date
        }
        cluster.update(values)

    def license_dispatch(self, cluster_id=-1, hostips_=None):
        license_dir = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + '/../../../manage/license')
        if not (os.path.isdir(license_dir) and os.listdir(license_dir)):
            return
        retry_count = 0
        while retry_count < 16:
            retry_count += 1
            is_retry = self._license_dispatch(cluster_id=cluster_id, hostips_=hostips_)

            if not is_retry:
                break
             
    def _license_dispatch(self, cluster_id=-1, hostips_=None):
        home = os.path.split(self.defs.UMP_HOME)[0]
        clusters = _sw.db_clusters()
        hosts = []
        nofreehosts = []
        for cluster in clusters:
            clusterhosts =  cluster.hosts

            if int(cluster.id) == int(cluster_id) and isinstance(hostips_,list):
                hostips = [{'ip':host_,'size':0}  for host_ in hostips_]
            else:
                hostips = [{'ip':host.ip,'size':int(host._license_total)*1000} for host in clusterhosts if host.license_total]
            hostips = sorted(hostips,key=lambda x :x.get('ip'))
            hosts.extend(hostips)

            freehost = [host.node_register_info for host in clusterhosts if host.node_register_info == 'free']
            if not len(freehost) == len(clusterhosts):
                is_free = True
                nofreehosts.extend(hostips)

        license_node = '/tmp/license_node'
        out = open(license_node,'wr')
        for va in  hosts:
            ip = va.get('ip')
            size = va.get('size')
            out.write('%s %s\n'%(ip,size))
            LOG.info('licese dispatch - %s - %s '%(ip,size))
        out.close()

        recode,out,err = utils._exec_pipe('python %s/manage/manage.py --dispatch %s'%(home,license_node))
        if err and "license_dispatch.lock" in err :
            return True
        if 'distribution' in out:
            out = open(license_node,'wr')
            for va in  nofreehosts:
                ip = va.get('ip')
                size = va.get('size')
                out.write('%s %s\n'%(ip,size))
                LOG.info('licese dispatch - %s - %s '%(ip,size))
            out.close()

        recode,out,err = utils._exec_pipe('python %s/manage/manage.py --dispatch %s'%(home,license_node))
        if err and "license_dispatch.lock" in err :
            return True

        LOG.info('licese dispatch - %s - %s '%(out,err))
        return False 
                
    def all_license_sync(self, skipid=-1):
        clusters = _sw.db_clusters() 
        for i in clusters:
            if int(i.id) == int(skipid):
                continue
            try:
                self.license_sync(i.id)
            except Exception, e:
                LOG.info(e)
    
    def _start_controller(self):
        return 
        code, localmd5, err = utils._exec_pipe('ump-controller-server --start')

        

 
if __name__ == '__main__':
    cm = ClusterManager()
    cm.cluster_host_sync(1)

